﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Stauffware.Common.Core
{
	//#################################################################################
	/// <summary>
	/// Abstract base class for floating-point parsers.
	/// </summary>
	/// <typeparam name="T">the type of the parsed result (single or double)</typeparam>

	public abstract class FloatParser<T> where T : struct
	{
		/// <summary>
		/// Enum bits reserved for NumberParseOptions.  
		/// "Derived" parse option enums must not redefine these.
		/// </summary>
		internal const int Reserved_ParseOptions_Bits = 0xFFFF00;

		/// <summary>
		/// What to use when there isn't a NumberParseOptions parameter.
		/// </summary>
		public const NumberParseOptions DefaultParseOptions = 
			NumberParseOptions.AllowWhiteSpace | NumberParseOptions.ParseEntireInput | 
			NumberParseOptions.AllowExponent | NumberParseOptions.AllowDecimalPoint | 
			NumberParseOptions.AllowLeadingSign | NumberParseOptions.AllowTrailingSign | 
			NumberParseOptions.AllowGrouping;

		/// <summary>
		/// Gets the maximum number of digits (base 10) that a number can have.
		/// </summary>
		protected abstract int MaxDigits { get; }

		/// <summary>
		/// Creates a Parse object of the proper class for this Parser.
		/// </summary>
		/// <returns></returns>
		protected abstract FloatParse<T> CreateParseObject ( );

		/// <summary>
		/// Attempts a parse of a normalized number string.
		/// </summary>
		/// <param name="str">
		///		The string, which must contain 1 or more decimal digits, optionally 
		///		preceded by a '-', optionally followed by an exponent.
		///		No whitespace, no separators.
		/// </param>
		/// <param name="value">receives the parsed value</param>
		/// <returns>true on success, false on failure</returns>
		protected abstract bool TryParse (string str, out T value);

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Parses a floating-point number.
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <param name="options">parsing options</param>
		/// <param name="ifp">formatting info, or null</param>

		protected FloatParse<T> _Parse (string input, NumberParseOptions options, IFormatProvider ifp)
		{
			Require.NotNull(input);

			NumberLexer lexer = new NumberLexer(options, null, MaxDigits, 3);

			bool lexedAnumber = lexer.LexNumber(input);

			FloatParse<T> parse = this.CreateParseObject();
			parse._parsedText = input.Substring(0, lexer.CharsLexed);
			parse._errorMessage = lexer.ErrorMessage;

			if (!lexedAnumber)
				return parse;

			string rawNumber = lexer.DigitsLeft;

			if (lexer.IsNegative)
				rawNumber = "-" + rawNumber;

			if (!string.IsNullOrEmpty(lexer.ExponentDigits))
			{
				if (lexer.ExponentDigits.Length > 3)
				{
					parse._errorMessage = "exponent out of range";
					return parse;
				}

				rawNumber += "E" + lexer.ExponentDigits;
			}

			T tempValue;
			if (this.TryParse(rawNumber, out tempValue))
				parse._value = tempValue;
			else parse._errorMessage = "number is too large";

			return parse;
		}

		//-----------------------------------------------------------------------------
	}

	//#################################################################################
	/// <summary>
	/// Encapsulates the result of trying to parse a string into a floating-point type.
	/// </summary>

	public abstract class FloatParse<T> : ValueParse<T> where T : struct
	{


	}

	//#################################################################################
	/// <summary>
	/// Parser for float values (System.Single).
	/// </summary>

	public class SingleFloatParser : FloatParser<float>
	{
		/// <summary>
		/// Attempts to parse a string for value of type short (System.Single).
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <param name="options">parsing options</param>
		/// <param name="ifp">an optional IFormatProvider</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public SingleFloatParse Parse (string input, NumberParseOptions options, IFormatProvider ifp)
		{ return (SingleFloatParse) this._Parse(input, options, ifp); }

		/// <summary>
		/// Attempts to parse a string for value of type short (System.Single),
		/// using NumberParseOptions.None.
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public SingleFloatParse Parse (string input)
		{ return (SingleFloatParse) this._Parse(input, NumberParseOptions.None, null); }

		/// <summary>
		/// Attempts to parse a string for value of type short (System.Single).
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <param name="options">parsing options</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public SingleFloatParse Parse (string input, NumberParseOptions options)
		{ return (SingleFloatParse) this._Parse(input, options, null); }

		/// <summary>
		/// Attempts to parse a string for value of type short (System.Single),
		/// using NumberParseOptions.None.
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <param name="ifp">an optional IFormatProvider</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public SingleFloatParse Parse (string input, IFormatProvider ifp)
		{ return (SingleFloatParse) this._Parse(input, NumberParseOptions.None, ifp); }

		/// Gets the maximum number of digits (base 10) that a number can have.
		protected override int MaxDigits { get { return 300; } }

		/// Creates a Parse object of the proper class for this Parser.
		protected override FloatParse<float> CreateParseObject ( )
		{ return new SingleFloatParse(); }

		/// Attempts a parse of a normalized number string.  (see FloatParser.TryParse)
		protected override bool TryParse (string str, out float value)
		{ return float.TryParse(str, out value); }

	}

	//=================================================================================
	/// <summary>
	/// Encapsulates the result of trying to parse a string into a single value.
	/// </summary>

	public class SingleFloatParse : FloatParse<float>
	{
	}

	//#################################################################################
	/// <summary>
	/// Parser for double values (System.Double).
	/// </summary>

	public class DoubleFloatParser : FloatParser<double>
	{
		/// <summary>
		/// Attempts to parse a string for value of type ushort (System.Double).
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <param name="options">parsing options</param>
		/// <param name="ifp">an optional IFormatProvider</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public DoubleFloatParse Parse (string input, NumberParseOptions options, IFormatProvider ifp)
		{ return (DoubleFloatParse) this._Parse(input, options, ifp); }

		/// <summary>
		/// Attempts to parse a string for value of type ushort (System.Double),
		/// using NumberParseOptions.None.
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public DoubleFloatParse Parse (string input)
		{ return (DoubleFloatParse) this._Parse(input, NumberParseOptions.None, null); }

		/// <summary>
		/// Attempts to parse a string for value of type ushort (System.Double).
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <param name="options">parsing options</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public DoubleFloatParse Parse (string input, NumberParseOptions options)
		{ return (DoubleFloatParse) this._Parse(input, options, null); }

		/// <summary>
		/// Attempts to parse a string for value of type ushort (System.Double),
		/// using NumberParseOptions.None.
		/// </summary>
		/// <param name="input">the string to be parsed</param>
		/// <param name="ifp">an optional IFormatProvider</param>
		/// <returns>an object containing the result of the parse attempt</returns>
		public DoubleFloatParse Parse (string input, IFormatProvider ifp)
		{ return (DoubleFloatParse) this._Parse(input, NumberParseOptions.None, ifp); }

		/// Gets the maximum number of digits (base 10) that a number can have.
		protected override int MaxDigits { get { return 300; } }

		/// Creates a Parse object of the proper class for this Parser.
		protected override FloatParse<double> CreateParseObject ( )
		{ return new DoubleFloatParse(); }

		/// Attempts a parse of a normalized number string.  (see FloatParser.TryParse)
		protected override bool TryParse (string str, out double value)
		{ return double.TryParse(str, out value); }
	}

	//=================================================================================
	/// <summary>
	/// Encapsulates the result of trying to parse a string into a double value.
	/// </summary>

	public class DoubleFloatParse : FloatParse<double>
	{
	}

	//#################################################################################
}
